#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sqlite3.h>
#include<gtk/gtk.h>
#include<pthread.h>


int sock;
sqlite3 *db;
GtkWidget *text_view_history;
GtkWidget *msg_entry;
//窗口退出时回收资源
void window_quit(GtkWidget *widget, gpointer data) {
    close(sock);
    sqlite3_close(db);
    exit(EXIT_SUCCESS);
}
//2，接收服务端的消息，并且显示



//接收消息的函数
void *receive_message() {
    // 定义接收消息的缓冲区
    char recv_buffer[1024 * 1024];
    for (;;) {
        // 清空缓冲区
        memset(recv_buffer, 0, sizeof(recv_buffer));

        // 接收服务端的消息
        // 服务端发送的消息的结尾有\0
        ssize_t ssize = recv(sock, recv_buffer, sizeof(recv_buffer) - 1, 0);
        if (ssize <= 0) {
            if (ssize == 0) {
                printf("服务器关闭连接\n");
                // 自行实现重连的功能
            }
            printf("接收消息失败\n");
            fflush(stdout);
        }
        // 接收成功
        char *msg = recv_buffer;
        printf("接收到的消息%s\n", msg);


        //1.显示到GtkWidget * text_view_history上
        // 2. GtkWidget *msg_entry 清空

        // 无法直接设置
        // gtk_text_buffer_set_text(text_view_history, msg, strlen(msg));

        // 获取buffer对象
        GtkTextBuffer *text_view_buffer = gtk_text_view_get_buffer(text_view_history);

        // 获取结尾的字符定位器
        GtkTextIter end_iter;
        gtk_text_buffer_get_end_iter(text_view_buffer, &end_iter);
        // 输入内容
        gtk_text_buffer_insert(text_view_buffer, &end_iter, msg, strlen(msg));
        // 输入回车
        gtk_text_buffer_insert(text_view_buffer, &end_iter, "\n", -1);

        // 清空内容
        gtk_entry_set_text(msg_entry, "");


        // 将消息写入数据库
        sqlite3 *stmt;
        char *save_chat_msg_sql = "insert into chat_history(direction, msg) values('receive',?)";
        int rc = sqlite3_prepare_v2(db, save_chat_msg_sql, -1, &stmt, NULL);
        if (rc != SQLITE_OK) {
            printf("转换失败 %s\n", sqlite3_errmsg(db));
        } else {
            printf("转换成功\n");
        }
        fflush(stdout);
        // 绑定？数据
        rc = sqlite3_bind_text(stmt, 1, msg, strlen(msg), NULL);
        if (rc != SQLITE_OK) {
            printf("绑定失败 %s\n", sqlite3_errmsg(db));
        } else {
            printf("绑定成功\n");
        }
        fflush(stdout);
        rc = sqlite3_step(stmt);
        if (rc != SQLITE_DONE) {
            printf("插入失败\n");
        } else {
            printf("插入成功\n");
        }
        fflush(stdout);
        sqlite3_finalize(stmt);

    }

}


int send_message(GtkWidget *widget, gpointer data) {
    g_print("用户点击了按钮\n");
    GtkWidget *msg_entry = data;
    const gchar *msg = gtk_entry_get_text(GTK_ENTRY(msg_entry));
    g_print("用户点击了按钮，消息的内容是：%s\n", msg);
    ssize_t ssize = send(sock, msg, strlen(msg), 0);
    if (ssize < 0) {
        perror("发送失败");
    }
    // 将消息写入数据库
    sqlite3 *stmt;
    char *save_chat_msg_sql = "insert into chat_history(direction, msg) values('send',?)";
    int rc = sqlite3_prepare_v2(db, save_chat_msg_sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        printf("转换失败 %s\n", sqlite3_errmsg(db));
    } else {
        printf("转换成功\n");
    }
    fflush(stdout);
    // 绑定？数据
    rc = sqlite3_bind_text(stmt, 1, msg, strlen(msg), NULL);
    if (rc != SQLITE_OK) {
        printf("绑定失败 %s\n", sqlite3_errmsg(db));
    } else {
        printf("绑定成功\n");
    }
    fflush(stdout);
    rc = sqlite3_step(stmt);
    if (rc != SQLITE_DONE) {
        printf("插入失败\n");
    } else {
        printf("插入成功\n");
    }
    fflush(stdout);
    sqlite3_finalize(stmt);
    return 0;
}


int main(int argc, char * argv[]) {
    //引入gtk
    gtk_init(&argc,&argv);
     //创建窗口
    GtkWidget *window =  gtk_window_new(GTK_WINDOW_TOPLEVEL);
    //设置窗口的标题和默认大小
    gtk_window_set_title(GTK_WINDOW(window),"CCNU聊天系统");
    gtk_window_set_default_size(GTK_WINDOW(window),600,500);

    //点击关闭按钮时，回收资源
    //不能直接关闭 todo
    g_signal_connect(window,"destroy",G_CALLBACK(window_quit),NULL);


    //在界面上堆放元素和布局
    // 1. 外层的上下结构的容器 container
    // 2. 将container加入到window
    GtkWidget *container = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
    gtk_container_add(GTK_CONTAINER(window), container);

    // 3. 创建标题label  label_title
    // 4. 放置label_title到container的最上面
    GtkWidget *label_title = gtk_label_new("Welcome to CCNU ChatBox");
    gtk_box_pack_start(GTK_BOX(container), label_title, FALSE, FALSE, 10); // different

    // 5. 构建滚动界面
    // 6. 放置滚动界面到container中
    GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
    gtk_box_pack_start(GTK_BOX(container), scrolled_window, TRUE, TRUE, 10);  // different

    // 7. 构建多行文本框
    // 8. 将多行文本框放置在滚动界面里
   text_view_history = gtk_text_view_new();
    gtk_container_add(GTK_CONTAINER(scrolled_window), text_view_history);

    // 9. 构建横向结构的布局的容器   hbox
    // 10.将横向布局的容器hbox放到container中
    GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
    gtk_box_pack_end(GTK_BOX(container), hbox, FALSE, FALSE, 10);

    // 11.构建单行文本框
    // 12.放置单行文本框到hbox中
    msg_entry = gtk_entry_new();
    gtk_box_pack_start(GTK_BOX(hbox), msg_entry, TRUE, TRUE, 10);

    // 13.构建发送按钮
    // 14.将发送按钮放到hbox中
    GtkWidget *button_send = gtk_button_new_with_label("Send");
    g_signal_connect(G_OBJECT(button_send), "clicked", G_CALLBACK(send_message), msg_entry);
    gtk_box_pack_end(GTK_BOX(hbox), button_send, FALSE, FALSE, 10);

    //显示窗口
    gtk_widget_show_all(window);


    const char *SERVER_NAME = "192.168.213.135";
    const int PORT = 9527;
    /*
        1. 根据域名获取ip地址  根据网络协议，一个域名可能对应多个IP  数组或者链表
        2. 使用connect函数连接socket
        3. 向socket中写入或者读取数据


        最后一步 释放资源
     */
    // 客户端的socket描述符
    sock = -1;
    struct addrinfo hints, *res, *p;
    char buffer[1024];

    // 初始化结构地址
    memset(&hints, 0, sizeof(hints));
    // IP层的协议
    hints.ai_family = AF_INET;
    // 传输层的协议   tcp协议
    hints.ai_socktype = SOCK_STREAM;

    // 指定端口号
    char port_str[6];
    snprintf(port_str, sizeof(port_str), "%d", PORT);


    int status = getaddrinfo(SERVER_NAME, port_str, &hints, &res);
    if (status != 0) {
        printf("获取IP地址错误 %s \n", gai_strerror(status));
        fflush(stdout);
        return 1;
    }
    // 遍历getaddrinfo
    for (p = res; p != NULL; p = p->ai_next) {
        // 创建socket
        sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if (sock == -1) {
            perror("socket创建失败");
            continue;
        }
        // 如果socket存在，使用socket进行连接
        int connectResult = connect(sock, p->ai_addr, p->ai_addrlen);
        if (connectResult == 0) {
            // 已经找到了server1对应的IP地址 并且连上
            break;
        }
        close(sock);
        sock = -1;
    }

    freeaddrinfo(res);
    if (sock == -1) {
        fprintf(stderr, "无法连接到服务器 %s:%d", SERVER_NAME, PORT);
        return 1;
    }
    // 已经连上了服务端
    printf("已经连上了服务器 %s:%d\n", SERVER_NAME, PORT);
    fflush(stderr);


    char *zErrMsg = 0;
    // 打开数据库
    //权限问题 /tmp和~目录 可以进行读写  如果不建在这些目录，则需要使用ACL进行解决权限
    int rc = sqlite3_open("/tmp/chat_system.db", &db);
    if (rc != SQLITE_OK) {
        printf("Can't open database: %s\n", sqlite3_errmsg(db));
        fflush(stdout);
        return EXIT_FAILURE;
    }
    char *create_table_sql = "create table if not exists chat_history (" \
                             "id        integer    primary key autoincrement," \
                             "direction text       comment '表示发送还是接收 send表示发送，receive表示接收'," \
                             "msg       text       comment '消息内容'," \
                             "event_time text default current_timestamp" \
                             ")";

    // 执行sql语句
    rc = sqlite3_exec(db, create_table_sql, NULL, NULL, &zErrMsg);
    if (rc != SQLITE_OK) {
        printf("建表失败 %s\n", zErrMsg);
        fflush(stdout);
        return 1;
    } else {
        printf("建表成功\n");
        fflush(stdout);
    }

    //接收消息  // 接收消息  开启多线程接收消息
    pthread_t recv_thread;
    int create_thread_result = pthread_create(&recv_thread,NULL, receive_message,NULL);
    if (create_thread_result != 0) {
        printf("创建接收消息的线程失败\n");
    } else {
        printf("创建接收消息的线程成功\n");
    }


    //保持界面
    gtk_main();
    //todo 待删除



    close(sock);
    return 0;
}
